#include "fault_predict_algo.hpp"
#include <ctime>
#include "com/otrd/Log/Config/server.hpp"

namespace ipmi
{

namespace otrd
{
    static constexpr auto eepromService = "com.otrd.Eeprom.Manager";
    static constexpr auto eepromFaultProCtlSwitchPath ="/com/otrd/eeprom/faultpre_ctrl";
    static constexpr auto eepromRawDataIntf = "com.otrd.Eeprom.Setting.RawData";
    static constexpr auto rawDataProp = "RawData";

    static constexpr auto eepromCPUFaultPrePath ="/com/otrd/eeprom/cpuFaultPredict";
    static constexpr auto eepromMemPcieFaultPrePath ="/com/otrd/eeprom/memPcieFaultPredict";
    static constexpr auto eepromFaultPreIntf = "com.otrd.Eeprom.FaultPre";
    static constexpr auto funnelProp = "faultPreFunnel";
    static constexpr auto thresholdProp = "faultPreThreshold";
    uint32_t CECounter::cpuFunnel = 30;
    uint32_t CECounter::memPcieFunnel = 60;
    uint32_t CECounter::cpuThreshold = 3000;
    uint32_t CECounter::memPcieThreshold = 6000;
    bool CECounter::isenable = true;
    std::string CECounter::getDeviceIdentifierString(const DeviceCECount& device) {  
        std::stringstream ss;  
        switch (device.type) {  
            case CPU: {  
                int cpu_id;  
                std::tie(cpu_id) = std::get<CPUIdentifier>(device.identifier);
                ss << "CPU" << cpu_id;
                break;  
            }  
            case MEMORY: {  
                int cpu_id, mem_id0, mem_id1;  
                std::tie(cpu_id, mem_id0, mem_id1) = std::get<MemoryIdentifier>(device.identifier);
                ss << "CPU" << cpu_id << "_C" << mem_id0 << "_D" << mem_id1; 
                break;  
            }  
            case PCIE: {  
                int param1, param2, param3, param4;  
                std::tie(param1, param2, param3, param4) = std::get<PCIEIdentifier>(device.identifier);
                ss << "PCIE:" << param1 << "_" << param2 << "_" << param3 << "_" << param4;  
                break;  
            }  
        }  
        return ss.str();  
    }  

    std::string uint8_t_to_hex_string(uint8_t value)
    {
        std::ostringstream oss;
        oss << std::hex << std::setw(2) << std::setfill('0') << static_case<int>(value);
        return oss.str();
    }

    std::string CECounter::getDeviceEvent(const DeviceCECount& device) {  
        std::string result;
        uint8_t eventbuf[3];
        switch (device.type) {  
            case CPU: {  
                int cpu_id;  
                std::tie(cpu_id) = std::get<CPUIdentifier>(device.identifier);
                eventbuf[0] = 0x01;
                eventbuf[1] = cpu_id & 0x0f;
                eventbuf[2] = 0;
                break;  
            }  
            case MEMORY: {  

                int cpu_id, mem_id0, mem_id1;  
                std::tie(cpu_id, mem_id0, mem_id1) = std::get<MemoryIdentifier>(device.identifier);
                eventbuf[0] = 0x01;
                eventbuf[1] = cpu_id;
                eventbuf[2] |= (mem_id0 << 4) & 0xf0;
                eventbuf[2] |=  mem_id1 & 0x0f;
                break;  
            }  
            case PCIE: {  
                int param1, param2, param3, param4;  
                std::tie(param1, param2, param3, param4) = std::get<PCIEIdentifier>(device.identifier);
                eventbuf[0] = 0x01;
                eventbuf[1] |= (param1 << 4) & 0xf0;
                eventbuf[1] |=  param2 & 0x0f;
                eventbuf[2] |= (param3 << 4) & 0xf0;
                eventbuf[2] |=  param4 & 0x0f; 
                break;  
            }  
        }  
        for(size_t i=0;i<sizeof(eventbuf)/sizeof(uint8_t);i++)
        {
            result += uint8_t_to_hex_string(eventbuf[i]);
        }
        return result;  
    }

    CECounter::CECounter(){
        PropertyValue value = getProperty(eepromFaultProCtlSwitchPath,eepromRawDataIntf,rawDataProp);
        if(std::holds_alternative<uint8_t>(value))
        {
            CECounter::isenable = (bool)std::get<uint8_t>(value);
        }
        value = getProperty(eepromCPUFaultPrePath,eepromFaultPreIntf,funnelProp);
        if(std::holds_alternative<uint32_t>(value))
        {
            CECounter::cpuFunnel = std::get<uint32_t>(value);
        }
        value = getProperty(eepromMemPcieFaultPrePath,eepromFaultPreIntf,funnelProp);
        if(std::holds_alternative<uint32_t>(value))
        {
            CECounter::memPcieFunnel = std::get<uint32_t>(value);
        }
        value = getProperty(eepromCPUFaultPrePath,eepromFaultPreIntf,thresholdProp);
        if(std::holds_alternative<uint32_t>(value))
        {
            CECounter::cpuThreshold = std::get<uint32_t>(value);
        }
        value = getProperty(eepromMemPcieFaultPrePath,eepromFaultPreIntf,thresholdProp);
        if(std::holds_alternative<uint32_t>(value))
        {
            CECounter::memPcieThreshold = std::get<uint32_t>(value);
        }
        std::cout << "FaultPredict:init: isenable=" << CECounter::isenable  
                        << ", cpuFunnel= " << CECounter::cpuFunnel << ", memPcieFunnel= " << CECounter::memPcieFunnel
                        << ", cpuThreshold= " << CECounter::cpuThreshold << ", memPcieThreshold= " << CECounter::memPcieThreshold
                        << std::endl;
        timer = std::thread([this]{
            while(true){
                std::this_thread::sleep_for(std::chrono::seconds(60));
                if(isenable){
                    timerCallback();
                }
            }
        });
    }

    void CECounter::addSEL(const std::string& path, const std::string& eventData)
    {
        if (path.empty())
        {
            return;
        }

        using namespace xyz::openbmc_project::Logging::SEL;
        using namespace phosphor::logging;
        using SELCreated =
            sdbusplus::xyz::openbmc_project::Logging::SEL::Error::Created;

        static constexpr auto recordType = 2;
        static constexpr auto generatorID = 0x20; // bmc

        report<SELCreated>(
            Created::RECORD_TYPE(recordType), Created::GENERATOR_ID(generatorID),
            Created::SENSOR_DATA(eventData.c_str()), Created::EVENT_DIR(true),
            Created::SENSOR_PATH(path.c_str()));
    }

    void CECounter::addIDL(const std::string& path, const std::string& eventData)
    {
        using LogConfig = sdbusplus::com::otrd::Log::server::Config;
        using IDLType = LogConfig::IDLType;
        using EventOffset = LogConfig::EventOffset;
        using IdlData = std::vector<std::tuple<uint64_t, IDLType, uint8_t, EventOffset, bool, std::string>>;

        IdlData idl;
        uint64_t timeStamp = static_cast<uint64_t>(time(nullptr)) * 1000; //单位ms
        IDLType idlType = IDLType::CE_STORM;
        uint8_t devIndex = 1;
        EventOffset evtOffset = EventOffset::IDL_CE_STORM;
        bool assert = true;
        std::string idlMsg = "Fault Predict CE storm";
        idl.push_back(std::make_tuple(timeStamp, idlType, devIndex, evtOffset, assert, idlMsg));

        crow::connections::systemBus->async_method_call(
        [asyncResp](const boost::system::error_code ec) {
        if (ec)
        {
        messages::internalError(asyncResp->res);
        return;
        }
        },
        "com.otrd.Log", "/com/otrd/log/config",
        "com.otrd.Log.Config", "Idllog", idl);

    }

    void CECounter::addSELForType(uint8_t type,const std::string& eventData)
    {
        switch(type)
        {
            case CPU:
                std::string sensorPath =
                    "/com/otrd/system/chassis/motherboard/process_storm";
                addIDL(sensorPath,eventData);
            break;
            case MEMORY:
                std::string sensorPath =
                    "/com/otrd/system/chassis/motherboard/memory_storm";
                addIDL(sensorPath,eventData);
            break;
            case PCIE:
                std::string sensorPath =
                    "/com/otrd/system/chassis/motherboard/pcie_storm";
                addIDL(sensorPath,eventData);
            break;
        }
    }

    void CECounter::addIDLForType(uint8_t type,const std::string& eventData)
    {
        switch(type)
        {
            case CPU:
                std::string sensorPath =
                    "/com/otrd/system/chassis/motherboard/process_fault";
                addSEL(sensorPath,eventData);
            break;
            case MEMORY:
                std::string sensorPath =
                    "/com/otrd/system/chassis/motherboard/memory_fault";
                addSEL(sensorPath,eventData);
            break;
            case PCIE:
                std::string sensorPath =
                    "/com/otrd/system/chassis/motherboard/pcie_fault";
                addSEL(sensorPath,eventData);
            break;
        }
    }

    std::string CECounter::getService(const std::string& path,
                                    const std::string& interface) const
    {

        using InterfaceList = std::vector<std::string>;
        std::map<std::string, std::vector<std::string>> mapperResponse;

        auto& bus = CECounter::getBus();

        auto mapper = bus.new_method_call(mapperBusName, mapperObjPath, mapperIntf,
                                        "GetObject");
        mapper.append(path, InterfaceList({interface}));

        auto mapperResponseMsg = bus.call(mapper);
        mapperResponseMsg.read(mapperResponse);
        if (mapperResponse.empty())
        {
            std::cout << "Failed to read getService mapper response, OBJECT_PATH = " << path << " INTERFACE = " << interface << std::endl;
            return "";
        }

        // the value here will be the service name
        return mapperResponse.cbegin()->first;
    }


    PropertyValue CECounter::getProperty(const std::string& objectPath,
                                       const std::string& interface,
                                       const std::string& propertyName) const
    {
        PropertyValue value{};

        auto& bus = CECounter::getBus();

        auto method = bus.new_method_call(eepromService, objectPath.c_str(),
                                        dbusPropertyIntf, "Get");
        method.append(interface, propertyName);

        auto reply = bus.call(method);
        reply.read(value);

        return value;
    }

    void CECounter::addCE(const DeviceCECount& device) {  
        std::lock_guard<std::mutex> lock(mutex_); 
        if(isenable == false)
            return;
        std::string id_str = getDeviceIdentifierString(device);
        if(ce_counts.find(id_str) == ce_counts.end()){
            ce_counts[id_str] = device;
        }  
        ce_counts[id_str].ce_count++; 
        ce_counts[id_str].ceCntPerSec++;
        std::cout << "FaultPredict:addCE: " << id_str  
                        << ", CE count: " << getCECount(ce_counts[id_str]) << std::endl;
    }  
    
    void CECounter::addCEFreq(const DeviceCECount& device) {  
        std::string id_str = getDeviceIdentifierString(device); 
        ce_counts[id_str].ceFreq++; 
    }

    void CECounter::removeCE(const DeviceCECount& device, int count) {    
        std::string id_str = getDeviceIdentifierString(device);  
        if (ce_counts.find(id_str) != ce_counts.end()) {  
            ce_counts[id_str].ce_count = std::max(0, ce_counts[id_str].ce_count - count);  
            ce_counts[id_str].ceCntPerSec = std::max(0, ce_counts[id_str].ceCntPerSec - count); 
        }  
    }  
  
     void CECounter::clearCE(const DeviceCECount& device,uint8_t type) {    
        std::string id_str = getDeviceIdentifierString(device);  
        if (ce_counts.find(id_str) != ce_counts.end()) {  
            switch(type)
            {
                case CLEAR_CE:
                    ce_counts[id_str].ce_count = 0;  
                break;
                case CLEAR_CE_PERSEC:
                    ce_counts[id_str].ceCntPerSec = 0;
                break;
                case CLEAR_CE_FREQ:
                    ce_counts[id_str].ceFreq = 0;
                break;
                case CLEAR_CE_ALL:
                    ce_counts[id_str].ce_count = 0;  
                    ce_counts[id_str].ceCntPerSec = 0;
                    ce_counts[id_str].ceFreq = 0;
                break;
            }
        }  
    }     

    int CECounter::getCECount(const DeviceCECount& device) {  
        std::string id_str = getDeviceIdentifierString(device);  
        if (ce_counts.find(id_str) != ce_counts.end()) {  
            return ce_counts[id_str].ce_count;  
        }  
        return 0;  
    }  
  
    bool CECounter::checkAlert(const DeviceCECount& device, int threshold) {  
        std::lock_guard<std::mutex> lock(mutex_); 
        uint32_t funnel = 0;
        if(device.type == CPU)
        {
            funnel = CECounter::cpuFunnel;
        }
        else
        {
            funnel = CECounter::memPcieFunnel;
        }
        removeCE(device, funnel);  
        int count = getCECount(device);
        //CE风暴处理,每秒钟内3次达到阈值，判定为CE风暴
        if(device.ceCntPerSec >= 3*threshold)
        {
            clearCE(device,CLEAR_CE_PERSEC);
            clearCE(device,CLEAR_CE);
            std::string event = getDeviceEvent(device);
            addIDLForType(device.type,event);
            return false;
        }
        else
        {
            if(count >= threshold)
            {
                clearCE(device,CLEAR_CE);
                return true;
            }
            return false;
        }
    }

    // 定时器回调函数  
    void CECounter::timerCallback(void) {   
        uint32_t threshold = 0;
        for (auto& it : ce_counts) {  
            DeviceCECount& device = it.second;  
            if(device.type == CPU)
            {
                threshold = CECounter::cpuThreshold;
            }
            else
            {
                threshold = CECounter::memPcieThreshold;
            }
            if (checkAlert(device, threshold)) {  
                std::string event = getDeviceEvent(device);
                addSELForType(device.type,event);
                std::cout << "FaultPredict:timerCallback:Alert " << it.first    
                        << ", CE count: " << getCECount(device) << std::endl;  
            } 
        }  
    } 

}//otrd
}//ipmi

